/** @file keyboard.c
 *
 * @author cameris camerisdump@gmail.com
 */

#include "keyboard.h"
#include "keycodes.h"
#include "keysymbols.h"
#include "keymap.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/atomic.h>

/** @name inputerrupt input/output variables
 * @{ */
static volatile int_fast8_t bitcount;
static volatile uint8_t answer;
static uint_fast8_t transmit;
static uint8_t scancode;
static uint_fast8_t parity;
/**@} */

/** @brief keyboard modifiers */
struct kbd_mod {
	unsigned int keyup:1;
	unsigned int e0:1;
	unsigned int lshift:1;
	unsigned int rshift:1;
	unsigned int lctrl:1;
	unsigned int rctrl:1;
	unsigned int alt:1;
	unsigned int altgr:1;
	unsigned int num:1;
	unsigned int scrl:1;
	unsigned int caps:1;
	//fn key?
};

struct kbd_mod mod; /**< @brief currently active keyboard modifiers */

#define KBD_RESET_ON_CNT 3 /**< @brief reset on successively receiving 0xAA */
#define KBD_RETURN_ACK		0xFA /**< @brief successfully received command */
#define KBD_RETURN_RESEND	0xFE /**< @brief asks for resending command */

/** @name scancode buffer variables
 * @{ */
volatile uint_fast8_t sc_bufsize;
uint_fast8_t sc_bufout;
uint_fast8_t sc_bufin;
uint8_t sc_buf[SC_BUF_SIZE];
/**@} */

/** @name key buffer variables
 * @{ */
volatile uint_fast8_t key_bufsize;
uint_fast8_t key_bufout;
uint_fast8_t key_bufin;
uint16_t key_buf[KEY_BUF_SIZE];
/**@} */

/** @name clock/data control
 * @{ */
#define CLOCK_LOW()		KBD_PORT &= ~(1<<KBD_CLOCK); \
						KBD_DDR |= (1<<KBD_CLOCK)

#define CLOCK_HIGH()	KBD_DDR &= ~(1<<KBD_CLOCK); \
						KBD_PORT |= (1<<KBD_CLOCK)

#define DATA_LOW()		KBD_PORT &= ~(1<<KBD_DATA); \
						KBD_DDR |= (1<<KBD_DATA)

#define DATA_HIGH()		KBD_DDR &= ~(1<<KBD_DATA); \
						KBD_PORT |= (1<<KBD_DATA)
/* @} */

/** @name interrupt control
 * @{ */
#define SET_INT()		KBD_INT_REG |= (1<<KBD_INT)
#define CLR_INT()		KBD_INT_REG &= ~(1<<KBD_INT)
/* @} */

/** @name interrupt sense
 * @{ */
#define INT_IS_FALLING_EDGE()	!(KBD_INT_CTRL & (1<<INT_ISC0))

#define INT_SET_FALLING_EDGE()	KBD_INT_CTRL &= ~(1<<INT_ISC0); \
								KBD_INT_FLAG_REG |= (1<<KBD_INT_FLAG)

#define INT_SET_RISING_EDGE() 	KBD_INT_CTRL |= (1<<INT_ISC0); \
								KBD_INT_FLAG_REG |= (1<<KBD_INT_FLAG)
/* @} */


/** @brief send data to keyboard
 *
 * sends data to keyboard. if KBD_RETURN_RESEND is received it tries to resend
 * the command a number of times.
 *
 * @param data data to send
 * @return answer of the keyboard */
static uint8_t kbd_send(uint8_t data)
{ /* {{{ */
	uint8_t try = 3;

	do {
		try--;

		/* clear interrupt */
		CLR_INT();

		transmit = 2;
		parity = 0;
		answer = 0;
		scancode = data;
		INT_SET_FALLING_EDGE();

		/* pull clock low and wait for kbd to recognize */
		CLOCK_LOW();
		_delay_us(100);
		DATA_LOW();

		/* enable interrupt */
		SET_INT();

		/* kbd takes over the clock signal */
		CLOCK_HIGH();

		/* wait for answer */
		while (!answer);

		if (answer != KBD_RETURN_RESEND) {
			break;
		}
	} while(try);

	return answer;
} /* }}} */

/** @brief set leds for currently active locks */
static void kbd_set_leds(void)
{ /* {{{ */
	kbd_send(0xED);
	kbd_send( (mod.caps<<2)|(mod.num<<1)|(mod.scrl) );
} /* }}} */

/** @brief insert scancode into buffer
 * @param sc scancode to insert */
static void kbd_put_sc(uint8_t sc)
{ /* {{{ */
	if (sc_bufsize < SC_BUF_SIZE) {
		sc_buf[sc_bufin] = sc;
		if ( ++sc_bufin >= SC_BUF_SIZE ) {
			sc_bufin = 0;
		}
		sc_bufsize++;
	}
} /* }}} */

/** @brief get and remove scancode from buffer
 * @return scancode, 0 means buffer is empty */
static uint8_t kbd_get_sc(void)
{ /* {{{ */
	uint8_t sc;
	ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
		if (sc_bufsize == 0) {
			return 0;
		}

		sc = sc_buf[sc_bufout];
		if ( ++sc_bufout >= SC_BUF_SIZE ) {
			sc_bufout = 0;
		}
		sc_bufsize--;
	}
	return sc;
} /* }}} */

/** @brief insert key into buffer
 * @param key key to insert */
static void kbd_put_key(uint16_t key)
{ /* {{{ */
	if (key_bufsize < KEY_BUF_SIZE) {
		key_buf[key_bufin] = key;
		if ( ++key_bufin >= KEY_BUF_SIZE ) {
			key_bufin = 0;
		}
		key_bufsize++;
	}
} /* }}} */

/** @brief map keycode to key and put key into key buffer
 * @param kc keycode
 * @param map mapping table
 * @return 0 if key was found, 1 otherwise */
static uint_fast8_t keycode_to_key(uint8_t kc, const __flash struct keymap *map)
{ /* {{{ */
	uint8_t tmp = map->keycode;
	uint16_t key;

	while ((tmp) && (tmp != kc)) {
		map++;
		tmp = map->keycode;
	}

	if (tmp == kc) {
		key = map->symbol ;
		if (key != NO_KEY) {
			kbd_put_key(key);
			return 0;
		}
	}
	return 1;
} /* }}} */

/** @brief map scancode to keycode
 * @param sc scancode
 * @param map mapping table
 * @return keycode */
static uint8_t scancode_to_keycode(uint8_t sc, const __flash struct codemap *map)
{ /* {{{ */
	uint8_t tmp = map->scancode;

	while ((tmp) && (tmp != sc)) {
		map++;
		tmp = map->scancode;
	}
	if (tmp == sc) {
		return map->keycode;
	}
	return NO_KEYCODE;
} /* }}} */

/** @brief decodes the whole scancode buffer to keys */
static void scancode_decode(void)
{ /* {{{ */
	static uint_fast8_t resetcnt = 0;
	uint_fast8_t data;
	uint_fast8_t mods;
	while ((data = kbd_get_sc()) && (key_bufsize < KEY_BUF_SIZE)) {
		if (data != 0xAA) {
			resetcnt = 0;
		} else {
			if (KBD_RESET_ON_CNT == ++resetcnt) {
				kbd_init();
				kbd_init_after_sei();
				return;
			}
		}

		switch (data) {
			case 0xF0:
				mod.keyup = 1;
				continue;
			case 0xE0:
				mod.e0 = 1;
				continue;
			//TODO 0xE1
		}

		if (mod.e0) {
			data = scancode_to_keycode(data, keycode_e0_map);
			mod.e0 = 0;
		} else {
			data = scancode_to_keycode(data, keycode_map);
		}

		if (NO_KEYCODE == data) continue;

		if (!mod.keyup) {
			switch (data) {
				case KC_ALT:
					mod.alt = 1;
					continue;
				case KC_ALTGR:
					mod.altgr = 1;
					continue;
				case KC_LSHIFT:
					mod.lshift = 1;
					continue;
				case KC_RSHIFT:
					mod.rshift = 1;
					continue;
				case KC_LCTRL:
					mod.lctrl = 1;
					continue;
				case KC_RCTRL:
					mod.rctrl = 1;
					continue;
				case KC_CAPS:
					kbd_set_capslock(!mod.caps);
					continue;
				case KC_NUM:
					kbd_set_numlock(!mod.num);
					continue;
			} /* get key */
		} else {
			switch (data) {
				case KC_ALT:
					mod.alt = 0;
					break;
				case KC_ALTGR:
					mod.altgr = 0;
					break;
				case KC_LSHIFT:
					mod.lshift = 0;
					break;
				case KC_RSHIFT:
					mod.rshift = 0;
					break;
				case KC_LCTRL:
					mod.lctrl = 0;
					break;
				case KC_RCTRL:
					mod.rctrl = 0;
					break;
			}
			mod.keyup = 0;
			continue; /* nothing else todo */
		}

		mods = 0;
		mods |= (mod.altgr)<<1;
		mods |= (mod.lctrl|mod.rctrl)<<2;
		mods |= (mod.alt)<<4;

		/* apply caps/num if no other modifiers are active */
		if (!mods) {
			if (data > 0 && data < LAST_CAPS_EFFECTED) {
				mods |= (mod.lshift|mod.rshift) ^ mod.caps;
			} else if (data > LAST_CAPS_EFFECTED && data < LAST_NUM_EFFECTED) {
				mods |= (mod.lshift|mod.rshift) ^ mod.num;
			} else {
				mods |= (mod.lshift|mod.rshift);
			}
		} else {
			mods |= (mod.lshift|mod.rshift);
		}

		switch (mods) {
			case 0x00: // no modifiers
				keycode_to_key(data, keymap_default);
				break;
			case 0x01: // shift
				keycode_to_key(data, keymap_shift);
				break;
			case 0x02: // altgr
				keycode_to_key(data, keymap_altgr);
				break;
			case 0x03: // shift + altgr
				keycode_to_key(data, keymap_shift_altgr);
				break;
			case 0x04: // ctrl
				keycode_to_key(data, keymap_ctrl);
				break;
			case 0x05: // shift + ctrl
				keycode_to_key(data, keymap_shift_ctrl);
				break;
			case 0x06: // ctrl + altgr
				keycode_to_key(data, keymap_ctrl_altgr);
				break;
		}
	}
} /* }}} */

/** @brief get and remove key from buffer
 * @return key */
uint16_t kbd_get_key(void)
{ /* {{{ */
	scancode_decode();
	if (key_bufsize <= 0) {
		return NO_KEY;
	}

	uint16_t key = key_buf[key_bufout];
	if ( ++key_bufout >= KEY_BUF_SIZE ) {
		key_bufout = 0;
	}
	key_bufsize--;

	return key;
} /* }}} */

/** @brief initializes the keyboard variables */
void kbd_init(void)
{ /* {{{ */
	CLR_INT();

	bitcount = 11;
	transmit = 0;

	mod.keyup=0;
	mod.e0=0;
	mod.lshift=0;
	mod.rshift=0;
	mod.lctrl=0;
	mod.rctrl=0;
	mod.altgr=0;

	key_bufsize = key_bufout = key_bufin = 0;
	sc_bufsize = sc_bufout = sc_bufin = 0;

	DATA_HIGH();
	CLOCK_LOW();

	KBD_INT_CTRL = (1<<INT_ISC1);
	INT_SET_FALLING_EDGE();

	SET_INT();

} /* }}} */

/** @brief initializes the keyboard itself, need interrupts to be enabled ( sei() ) */
void kbd_init_after_sei(void)
{ /* {{{ */
	/* reset */
	kbd_send(0xFF);
	/* wait for BAT (Basic Assurance Test) to complete */
	_delay_ms(750);

	/* set typmatic rate/delay */
	kbd_send(0xF3);
	kbd_send(KBD_TYPEMATIC);

	/* set locks and leds */
	mod.num = 0;
	mod.caps = 0;
	mod.scrl = 0;
	kbd_set_leds();
} /* }}} */

/** @brief reenables the keyboard */
void kbd_enable(void)
{ /* {{{ */
	kbd_send(0xF4);

	/* set typmatic rate/delay */
	kbd_send(0xF3);
	kbd_send(KBD_TYPEMATIC);

	kbd_set_leds();
} /* }}} */

/** @brief disables the keyboard
 *
 * disables the keyboard, so no scancodes are sent anymore */
void kbd_disable(void)
{
	kbd_send(0xF5);
}

/** @brief set num lock
 * @param b bool 1 = ON or 0 = OFF */
void kbd_set_numlock(uint_fast8_t b)
{ /* {{{ */
	if (b != 0) b = 1;
	mod.num = b;

	kbd_set_leds();
} /* }}} */

/** @brief set caps lock
 * @param b bool 1 = ON or 0 = OFF */
void kbd_set_capslock(uint_fast8_t b)
{ /* {{{ */
	if (b != 0) b = 1;
	mod.caps = b;

	kbd_set_leds();
} /* }}} */

/** @brief set scroll lock
 * @param bool 1 = ON or 0 = OFF */
void kbd_set_scrolllock(uint_fast8_t b)
{ /* {{{ */
	if (b != 0) b = 1;
	mod.scrl = b;

	kbd_set_leds();
} /* }}} */

/** @brief clock interrupt routine
 *
 * clock interrupt routine that handles the receiving/sending
 * of data from/to the keyboard */
ISR(KBD_INT_VECT)
{ /* {{{ */
	if (INT_IS_FALLING_EDGE()) {
		INT_SET_RISING_EDGE();
		if (bitcount > 2 && bitcount < 11) {
			scancode >>= 1;
			if ( transmit != 2 && (KBD_PIN & (1<<KBD_DATA)) ) {
				scancode |= 0x80;
			}
		}
	} else {
		INT_SET_FALLING_EDGE();
		if( --bitcount == 0 ) {
			bitcount = 11;
			if (transmit == 2) {
				/* send complete, expecting answer */
				transmit--;
			} else if (transmit == 1) {
				/* received scancode is answer to cmd */
				answer = scancode;
				transmit--;
			} else {
				kbd_put_sc(scancode);
			}
		}
		/* when sending, set data pin before it is read by kbd on falling edge */
		if (transmit == 2) {
			if (bitcount > 2 && bitcount < 11) {
				if (scancode & 1) {
					KBD_PORT |= (1<<KBD_DATA);
					parity++;
				} else {
					KBD_PORT &= ~(1<<KBD_DATA);
				}
			} else if (bitcount == 2) {
				if( !(parity & 1) ) {
					KBD_PORT |= (1<<KBD_DATA);
				} else {
					KBD_PORT &= ~(1<<KBD_DATA);
				}
			} else if (bitcount == 1) {
				DATA_HIGH();
			}
		}
	}
} /* }}} */

/* vim:set foldmethod=marker: */
